package com.kevin.netty.starter.util;

import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ChannelGroupHelper {

    public static final String BIND_ID_KEY = "bindId"; // 绑定关系
    public static final String HEARTBEAT_KEY = "heartbeat"; // 心跳
    public static final String CREATE_TIME_KEY = "createTime"; // 创建时间
    public static final String ACTIVE_TIME_KEY = "activeTime"; // 活跃时间

    private static final AttributeKey<Session> ATTRIBUTE_KEY = AttributeKey.valueOf(BIND_ID_KEY);
    private static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private static final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();

    /**
     * 添加通道
     *
     * @param channel 通道
     */
    public boolean add(Channel channel) {
        boolean added = channelGroup.add(channel);
        if (added) {
            log.info("<<<<<< new channel connected:{} <<<<<<", channel.id().asShortText());
        }
        return added;
    }

    /**
     * 注册绑定
     * 1、设置 channel 中 bindId 属性，形成绑定关系
     * 2、存放在 channelMap 中
     *
     * @param channel 通道
     * @param bindId  绑定关系
     */
    public void register(Channel channel, String bindId) {
        this.setChannelBindId(channel, bindId);
        channelMap.put(bindId, channel);
        log.info("<<<<<< channel registered,channelId:{},bindId:{} <<<<<<", channel.id().asShortText(), bindId);
    }

    /**
     * 注销绑定
     * 1、已注册绑定 channel 关闭
     * 2、未注册绑定 channel 关闭
     *
     * @param channel 通道
     */
    public void unregister(Channel channel) {
        String bindId = this.getBindIdByChannel(channel);
        if (null != bindId) {
            channelMap.remove(bindId);
            this.removeByBindId(bindId);
        } else {
            this.remove(channel);
        }
        log.warn(">>>>>> channel unregistered,channelId:{},bindId:{} >>>>>>", channel.id().asShortText(), bindId);
    }


    /**
     * 移除并关闭通道
     * 正常情况下 channelGroup 会自动回收 channel
     * 一些特定场景需要手动移除
     *
     * @param channel 通道
     */
    public void remove(Channel channel) {
        boolean removed = channelGroup.remove(channel);
        if (removed) {
            channel.close();
        }
    }

    /**
     * 移除并关闭所有绑定关系的通道
     *
     * @param bindId 绑定关系
     */
    public void removeByBindId(String bindId) {
        if (null != bindId) {
            channelGroup.forEach((Channel channel) -> {
                String bindingId = this.getBindIdByChannel(channel);
                if (bindId.equals(bindingId)) {
                    this.remove(channel);
                }
            });
        }
    }

    /**
     * 获取绑定关系的通道
     *
     * @param bindId 绑定关系
     * @return channel 通道
     */
    public Channel getChannelByBindId(String bindId) {
        return channelMap.get(bindId);
    }

    /**
     * 设置 channel 中 bindId 属性，形成绑定关系
     *
     * @param channel 通道
     * @param bindId  绑定关系
     */
    private void setChannelBindId(Channel channel, String bindId) {
        long currentTime = System.currentTimeMillis();
        LinkedHashMap<String, Object> attribute = new LinkedHashMap<>();
        attribute.put(BIND_ID_KEY, bindId); // 绑定关系
        attribute.put(CREATE_TIME_KEY, currentTime); // 创建时间
        attribute.put(ACTIVE_TIME_KEY, currentTime); // 活跃时间，按需更新最近时间
        attribute.put(HEARTBEAT_KEY, 0); // 初始值：0，按需处理，举例：每次超时空闲链接+1，超过3次主动断开链接
        this.setSessionAttribute(channel, attribute);
    }

    /**
     * 获取 channel 的绑定关系
     *
     * @param channel 通道
     * @return bindId 绑定关系
     */
    public String getBindIdByChannel(Channel channel) {
        Object attribute = this.getSessionAttribute(channel, BIND_ID_KEY);
        return (null != attribute) ? attribute.toString() : null;
    }

    /**
     * 获取 channel 附加属性
     *
     * @param channel      通道
     * @param attributeKey 附加属性 Key
     * @return 附加属性
     */
    public Object getSessionAttribute(Channel channel, String attributeKey) {
        Session session = this.getSession(channel);
        if (null != session && null != session.get(attributeKey)) {
            return session.get(attributeKey);
        }
        return null;
    }

    /**
     * 设置 channel 附加属性
     *
     * @param channel   通道
     * @param attribute 附加属性
     */
    public void setSessionAttribute(Channel channel, Map<String, Object> attribute) {
        Session session = this.getSession(channel);
        if (null != session) {
            session.putAll(attribute);
        }
        if (null == session) {
            session = new Session();
            session.putAll(attribute);
        }
        channel.attr(ATTRIBUTE_KEY).setIfAbsent(session);
    }

    /**
     * 获取 Session
     *
     * @param channel 通道
     * @return Session
     */
    public Session getSession(Channel channel) {
        return channel.attr(ATTRIBUTE_KEY).get();
    }

    public String getBindId() {
        return BIND_ID_KEY;
    }

    public AttributeKey<Session> getAttributeKey() {
        return ATTRIBUTE_KEY;
    }

    public ChannelGroup getChannelGroup() {
        return channelGroup;
    }

    public ConcurrentHashMap<String, Channel> getChannelMap() {
        return channelMap;
    }

}